package lng

import (
	"errors"
	"fmt"
	"strings"
)

// 一个使用比较广泛的包：[golang.org/x/text/language](https://pkg.go.dev/golang.org/x/text/language)
// 已注册的语言：https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
// 可在线查询：https://iso639-3.sil.org/code_tables/639/data
// 可在线查询：https://r12a.github.io/app-subtags/
// 使用方法介绍：https://www.w3.org/International/questions/qa-choosing-language-tags.zh-hans.html

// 知识点：
// - 宏语言（Macrolanguage）：是指一组密切相关的语言，这些语言在语言学上通常被认为是同源的，
//       或者在实际使用中可以相互理解。宏观语言的代码用于将这些相关的语言聚合在一起，以便于在需要处理较大语言群体时使用。
//       例如 zh 是宏语言，它是 cmn（普通话）、yue（粤语）、nan（闽南语）等语言，都属于 zh 这个宏语言。
//       不建议直接使用宏语言标签，建议直接使用独立（Individual）语言标签，如 yue 。但也有例外：
//       zh 虽然是宏语言，由于历史原因，zh 绝大多数情况下是指汉语中的普通话，如果使用 cmn 反而可能有兼容问题；
//       除此之外，应当使用 yue、nan 等独立（Individual）语言标签。
// - 多代码标签：在 ISO-639 中一种语言可能有多个代码（如有一个双字母和一或两个三字母代码），IANA 注册中心会消除
//       这种歧义，优先双字母代码，无双字母代码时才使用三字母代码（如果有多个三字母代码则选其一）。
//       例如宏语言 zho（639-3, 639-2/T）、chi（639-2/B）、zh（639-1），IANA 选择双字母代码 zh。

const (
	ScopeIndividual    = "Individual"    // 表示一个独立的语言
	ScopeMacroLanguage = "Macrolanguage" // 表示一组密切相关的语言，这些语言通常共享很多词汇和语法结构，
	ScopeCollection    = "Collection"    // 一组存在亲缘关系、在同一地理区域使用或以其他方式相关的语言的合称，如：zhx 华语家族
	ScopeLocal         = "Local"         // 保留给本地使用的语言（目前范围为 qaa..qtz）
	ScopeSpecial       = "Special"       // 用于特殊目的语言（目前有 mis，mul，und，zxx 四个）
)

var (
	ErrInvalidTag = errors.New("invalid language tag") // 无效的语言标签，通常是格式错误
)

// 语言 ISO-639
type Language interface {
}

// 语言标签 BCP 47（RFC 5646）。
// 语言标签由一或多个子标签组成，子标签只能由字母和数字（a-zA-Z0-9）组成，子标签之间用 "-" 连接。
// 有一些无法与 RFC 5646 兼容的标签，称为旧版标签（grandfathered tag），在 IANA 注册中心单列出来。
type LanguageTag interface {
	// 主要语言子标签（primary language subtag），由 2 或 3 个字母组成。
	// 按惯例，实现者应当以小字形式返回主要语言子标签。
	Primary() string
	// 扩展语言子标签（extended language subtag, 缩写为 extlang）。由 3 个字母组成。
	// 通常这是为兼容历史规则，如 zh-yue 的主标签为 zh，扩展标签为 yue；现在直接使用 yue 主标签更好。
	// 扩展标签是 3 字母的，它本身就是已注册的独立语言，所以 主-扩展 与 扩展 的形式是等价的，如 zh-yue 与 yue 等价。
	// 只有在为了兼容时，才使用 主-扩展 形式的标签。
	// 目前只有 ar、kok、lv、ms、sw、uz、zh 七种宏语言和 sgn 可以存在扩展标签。
	// 注：Unicode的 CLDR （通用地区数据仓库）不支持扩展标签，所以使用 yue 而不是 zh-yue，官话使用 zh 而不是 cmn。
	ExtendedTags() []string
	// ExtendedTags() 中的第一个（通常只有一个，第二第三个永久保留不用），如果没有，则返回 ""。
	Extended() string
	// 文字子标签（Script subtag），由 4 个字母组成。
	// 一种语言可能有多种文字书写，此时如有必要，则添加 Script subtag。
	// 例如汉语可以由简体（zh-Hans）或繁体（zh-Hant）书写，又如象形文字拉丁化（如用汉语罗马化 zh-Latn）。
	Script() string
	// 地区子标签（Region subtag），指示语言的使用地区。由 2 个字母或 3 个数字组成。
	// 由 2 个字母组成的是国家与地区，如 CN（大陆地区），HK（香港地区）；由 3 个数字组成的是大区，如 030（东亚）。
	Region() string
	// 变体子标签（Variant subtag），通常与特定的文字（Script）标签下使用。
	// 由【5 到 8 个字母与数字】或者【1 个数字后接 3 个数字或字母】组成。由 4 个数字组成的，通常代表年份。
	// 多个变体子标签按重要性递减顺序出现，同等重要的按字母顺序出现。
	// 例如 pinyin 变体只能在 zh-Latn (汉字拉丁化) 与 bo-Latn （藏文拉丁化）后使用：zh-Latn-pinyin，bo-Latn-pinyin。
	// 汉字拉丁化有多种方案（变体），如
	//   zh-Latn-pinyin（汉语拼音）；
	//   zh-Latn-tongyong（通用，台湾）；
	//   zh-Latn-wadegile（威氏拼音）汉语拼音颁布后停用，英国人为方便外国人在华学习而制订的拉丁化方案。
	//      威氏拼音举例：Sun Yat-sen（孙中山）、Konfucius（孔子）
	VariantTags() []string
	Variant() string // VariantTags() 中的第一个，如果没有，则返回 ""
	// 扩展子标签（Extension Subtags）。由 1 个字母引入，后接一或多个由 2 到 8 个字符组成的子标签。
	// 扩展子标签是一种机制，用于扩展语言标签以在各种应用程序中使用。
	// 它们旨在识别通常与语言或语言标签相关联使用的信息，但这些信息不是语言识别的一部分。
	// 目前只有一个由 Unicode 联盟注册的子标签：u，如 de-DE-u-co-phonebk （表示应用应该使用电话本中的排序规则）。
	ExtensionTags() []string
	Extension() string // ExtensionTags() 中的第一个，如果没有，则返回 ""
	// 私用子标签（Private Use Subtags）。由 1 个字母 x 引入，后接一或多个不超过 8 字符的子标签。
	// 私用子标签由使用它们的各方之间的私人协议制定和维护，例如 en-US-x-twain 只有使用它的人才知道什么意思。
	// 对于 en-US-x-twain 则返回 ["x", "twain"]
	PrivateUseTags() []string
	PrivateUse() string // 返回使用 - 连接起来的私用子标签，如 ["x", "twain"] 则返回 "x-twain"
	// 标记位：
	//  -1 - 仅符合 RFC 5646 的标签，但未验证是否已在 IANA 中心注册 或 ISO 639 定义；
	//   0 - 符合 RFC 5646 且在 IANA 中心注册的正常标签；
	//   1 - 旧版标签（grandfathered tag）；
	//   2 - 冗余标签（redundant）；
	//   4 - 符合 RFC 5646 且语言未在 IANA 中心注册但已在 ISO 639-3 定义的标签（因为一种语言IANA只选择其中一个代码）；
	//   8 - 符合 RFC 5646 且语言未在 IANA 中心注册也未在 ISO 639-3 定义的标签；
	//  16 - 符合 RFC 5646 且文字子标签未在 IANA 中心注册的标签；
	//  32 - 符合 RFC 5646 且地区子标签未在 IANA 中心注册的标签；
	//  64 - 符合 RFC 5646 且变体子标签未在 IANA 中心注册的标签。
	// 例如：48 表示标签中的 文字子标签 与 地区子标签 未在 IANA 中心注册。
	Flags() int
	// 是否是同一语言。
	// 例如：yue 与 zh-yue 相同，zh 与 cmn 与 zh-cmn 相同；但 zh 与 zh-yue 并不相同。
	Same(LanguageTag) bool
	// 返回更合适的语言标签，主要针对 主语言 与 扩展语言 子标签。
	// 例如 zh-yue 返回 yue；zh-cmn 或 cmn 返回 zh。
	Preferred() LanguageTag
	// 生成为字符串
	fmt.Stringer
}

// 语言标签的默认实现
type langtag struct {
	primary    string
	extlangs   []string
	script     string
	region     string
	variants   []string
	extensions []string
	privates   []string
	flags      int
}

// 创建一个语言标签
func newlangtag() *langtag {
	return &langtag{
		extlangs:   make([]string, 0),
		variants:   make([]string, 0),
		extensions: make([]string, 0),
		privates:   make([]string, 0),
		flags:      -1,
	}
}

// 克隆一个语言标签
func (t *langtag) clone() (n *langtag) {
	n = newlangtag()
	n.primary, n.script, n.region, n.flags = t.primary, t.script, t.region, t.flags
	n.extlangs = append(n.extlangs, t.extlangs...)
	n.variants = append(n.variants, t.variants...)
	n.extensions = append(n.extensions, t.extensions...)
	n.privates = append(n.privates, t.privates...)
	return
}

// 主要语言子标签（primary language subtag），由 2 或 3 个字母组成。
func (t *langtag) Primary() string {
	return t.primary
}

// 扩展语言子标签（extended language subtag, 缩写为 extlang）。由 3 个字母组成。
func (t *langtag) ExtendedTags() []string {
	return t.extlangs
}

// 扩展语言子标签中的第一个，如果没有，则返回 ""。
func (t *langtag) Extended() string {
	if len(t.extlangs) > 0 {
		return t.extlangs[0]
	}
	return ""
}

// 文字子标签（Script subtag），由 4 个字母组成。
func (t *langtag) Script() string {
	return t.script
}

// 地区子标签（region subtag），由 2 个字母或 3 个数字组成。
func (t *langtag) Region() string {
	return t.region
}

// 变体子标签（Variant subtag），通常与特定的文字（Script）标签下使用。
func (t *langtag) VariantTags() []string {
	return t.variants
}

// 变体子标签中的第一个，如果没有，则返回 ""。
func (t *langtag) Variant() string {
	if len(t.variants) > 0 {
		return t.variants[0]
	}
	return ""
}

// 扩展子标签（Extension Subtags）。由 1 个字母引入，后接一或多个由 2 到 8 个字符组成的子标签。
func (t *langtag) ExtensionTags() []string {
	return t.extensions
}

// 扩展子标签中的第一个，如果没有，则返回 ""。
func (t *langtag) Extension() string {
	if len(t.extensions) > 0 {
		return t.extensions[0]
	}
	return ""
}

// 私用子标签（Private Use Subtags）。由 1 个字母 x 引入，后接一或多个不超过 8 字符的子标签。
// 例如 en-US-x-twain 会返回 ["x", "twain"]
func (t *langtag) PrivateUseTags() []string {
	return t.privates
}

// 私用子标签。例如 en-US-x-twain 则返回 "x-twain"
func (t *langtag) PrivateUse() string {
	if len(t.privates) > 0 {
		return t.privates[0]
	}
	return ""
}

// 标记位
func (t *langtag) Flags() int {
	return t.flags
}

// 返回更合适的标签
func (t *langtag) Preferred() LanguageTag {
	n := t
	if ext := n.Extended(); ext != "" { // 存在扩展子标签时
		n = n.clone()               // 复制一个
		n.extlangs = n.extlangs[:0] // 清空扩展子标签
		n.primary = Preferred(ext)  // 为扩展子标签选择更合适的，作为主语言子标签
	} else if better := Preferred(n.primary); better != n.primary { // 只有主语言子标签且有更多合适的子标签时
		n = n.clone()      // 复制一个
		n.primary = better // 设置主语言子标签为更合适的子标签
	}
	return n
}

// 两个标签是否相同
func (t *langtag) Same(other LanguageTag) bool {
	if other == nil || t == nil || t == other {
		return t == other
	}
	tp, op := t.Primary(), other.Primary()
	te, oe := t.Extended(), other.Extended()
	if te == oe {
		return Same(tp, op) // 扩展语言相同：有且相同，或都为空，则看主语言是否相同
	} else if te == "" {
		return Same(tp, oe) // t 的扩展语言为空，则用 t 的主语言与 other 的扩展语言比较
	} else if oe == "" {
		return Same(op, te) // other 的扩展语言为空，则用 other 的主语言与 t 的扩展语言比较
	} else {
		return false // 扩展语言都存在且不相同，那就是不同
	}
}

// 实现 fmt.Stringer 接口
func (t *langtag) String() string {
	sb := new(strings.Builder)
	sb.WriteString(t.primary)
	c := '-'
	for _, ext := range t.extlangs {
		sb.WriteRune(c)
		sb.WriteString(ext)
	}
	if t.script != "" {
		sb.WriteRune(c)
		sb.WriteString(t.script)
	}
	if t.region != "" {
		sb.WriteRune(c)
		sb.WriteString(t.region)
	}
	for _, v := range t.variants {
		sb.WriteRune(c)
		sb.WriteString(v)
	}
	for _, ext := range t.extensions {
		sb.WriteRune(c)
		sb.WriteString(ext)
	}
	for _, priv := range t.privates {
		sb.WriteRune(c)
		sb.WriteString(priv)
	}
	return sb.String()
}

// 解析语言标签
func (t *langtag) parse(tag string) error {
	if tl := len(tag); tl < 2 {
		return ErrInvalidTag // 长度小于 2，不是语言子标签
	} else if tl < 4 && IsAlphabetStr(tag) {
		t.primary = strings.ToLower(tag) // 仅由主要语言子标签组成
		return nil
	}
	parts := strings.Split(tag, "-")
	if len(parts) < 2 {
		return ErrInvalidTag // 仅由一个子标签组成，但这个子标签不是语言子标签，所以不符合 RFC 5646
	}
	if pl := len(parts[0]); pl < 2 || pl > 3 || !IsAlphabetStr(parts[0]) {
		return ErrInvalidTag // 主语言子标签不符合 RFC 5646
	}
	t.primary = strings.ToLower(parts[0])
	return t.parseExtLangToEnd(parts[1:])
}

// 解析扩展语言子标签及后续标签
func (t *langtag) parseExtLangToEnd(parts []string) error {
	if len(parts) == 0 {
		return nil
	}
	label := parts[0]
	switch len(label) {
	case 1:
		return t.parseExtension(parts) // 扩展或私用子标签
	case 2:
		return t.parseRegion(label, parts[1:]) // 地区子标签
	case 3:
		if IsDigitStr(label) {
			return t.parseRegion(label, parts[1:]) // 地区子标签
		} else if IsAlphabetStr(label) {
			t.extlangs = append(t.extlangs, strings.ToLower(label))
			return t.parseExtLangToEnd(parts[1:])
		} else {
			return ErrInvalidTag // 长度为3，但不是扩展语言子标签，也不是地区子标签
		}
	case 4:
		if IsDigitRune(label[0]) {
			return t.parseVariant(label, parts[1:]) // 以数字开头不是文字子标签，而是变体
		}
		return t.parseScript(label, parts[1:]) // 文字子标签
	case 5, 6, 7, 8:
		return t.parseVariant(label, parts[1:]) // 变体子标签
	default:
		return ErrInvalidTag // 非法的长度
	}
}

// 解析文字子标签
func (t *langtag) parseScript(label string, parts []string) error {
	t.script = strings.ToUpper(label[:1]) + label[1:] // 脚本子标签
	if len(parts) == 0 {
		return nil
	}
	label = parts[0]
	switch len(label) {
	case 1:
		return t.parseExtension(parts) // 扩展或私用子标签
	case 2:
		return t.parseRegion(label, parts[1:]) // 地区子标签
	case 3:
		if IsDigitStr(label) {
			return t.parseRegion(label, parts[1:]) // 地区子标签
		} else {
			return ErrInvalidTag // 长度为3，但不是地区子标签
		}
	case 4:
		if IsDigitRune(label[0]) {
			return t.parseVariant(label, parts[1:]) // 以数字开头不是文字子标签，而是变体
		} else {
			return ErrInvalidTag // 长度为4，但不是变体
		}
	case 5, 6, 7, 8:
		return t.parseVariant(label, parts[1:]) // 变体子标签
	default:
		return ErrInvalidTag // 非法的长度
	}
}

// 解析地区子标签
func (t *langtag) parseRegion(label string, parts []string) error {
	t.region = strings.ToUpper(label)
	if len(parts) == 0 {
		return nil
	}
	label = parts[0]
	switch len(label) {
	case 1:
		return t.parseExtension(parts) // 扩展或私用子标签
	case 4:
		if IsDigitRune(label[0]) {
			return t.parseVariant(label, parts[1:]) // 以数字开头不是文字子标签，而是变体
		} else {
			return ErrInvalidTag // 长度为4，但不是变体
		}
	case 5, 6, 7, 8:
		return t.parseVariant(label, parts[1:]) // 变体子标签
	default:
		return ErrInvalidTag // 非法的长度
	}
}

// 解析变体子标签
func (t *langtag) parseVariant(label string, parts []string) error {
	t.variants = append(t.variants, label)
	if len(parts) == 0 {
		return nil
	}
	label = parts[0]
	switch len(label) {
	case 1:
		return t.parseExtension(parts) // 扩展或私用子标签
	case 4:
		if IsDigitRune(label[0]) {
			return t.parseVariant(label, parts[1:]) // 以数字开头不是文字子标签，而是变体
		} else {
			return ErrInvalidTag // 长度为4，但不是变体
		}
	case 5, 6, 7, 8:
		return t.parseVariant(label, parts[1:]) // 变体子标签
	default:
		return ErrInvalidTag // 非法的长度
	}
}

// 解析扩展子标签
func (t *langtag) parseExtension(parts []string) error {
	if pl := len(parts); pl == 0 {
		return nil
	} else if pl < 2 {
		return ErrInvalidTag
	}
	var ext []string
	for i, p := range parts {
		if p == "x" || p == "X" {
			if len(ext) > 0 {
				t.extensions = append(t.extensions, strings.Join(ext, "-"))
			}
			return t.parsePrivateUse(parts[i:])
		}
		if pl := len(p); pl == 1 {
			if len(ext) > 0 {
				t.extensions = append(t.extensions, strings.Join(ext, "-"))
			}
			ext = []string{p}
		} else if pl <= 8 {
			ext = append(ext, p)
		} else {
			return ErrInvalidTag
		}
	}
	if len(ext) > 0 {
		t.extensions = append(t.extensions, strings.Join(ext, "-"))
	}
	return nil
}

// 解析私用子标签
func (t *langtag) parsePrivateUse(parts []string) error {
	if len(parts) <= 1 {
		return ErrInvalidTag
	}
	for _, x := range parts {
		if len(x) > 8 {
			return ErrInvalidTag
		}
	}
	parts[0] = strings.ToLower(parts[0])
	t.privates = parts
	return nil
}

// 解析语言标签，仅从形式上解析，而不验证是否已在 IANA 注册。
// 如果 tag 不符合 RFC 5646，则返回错误 ErrInvalidTag。
func ParseTag(tag string) (LanguageTag, error) {
	tag = strings.TrimSpace(tag)
	if !IsLangTagStr(tag) {
		return nil, ErrInvalidTag // 包括非法字符
	}
	ret := newlangtag()
	if err := ret.parse(tag); err != nil {
		return nil, err // 解析失败
	}
	return ret, nil
}

// 判断两个语言子标签是否相同。语言子标签是 2 位或 3 位英文字母组成的标签，如 zh、yue 等。
func Same(t1, t2 string) bool {
	return Preferred(t1) == Preferred(t2)
}

// 判断语言子标签是否有更合适的子标签，返回更合适的子标签。语言子标签是 2 位或 3 位英文字母组成的标签，如 zh、yue 等。
func Preferred(tag string) string {
	tag = strings.ToLower(strings.TrimSpace(tag))
	switch tag {
	case "cmn":
		return "zh"
	default:
		return tag
	}
}
